package com.raymond.redis;


import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.SerializationUtils;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author ：Void
 * @date ：2019/5/25 11:34
 * @tittle redis缓存调用的Java方法
 * @description：RedisConnectionFactory、RedisTemplate会自动装配
 * @version: v2.0
 * @see {@link org.springframework.boot.autoconfigure.data.redis}
 */
@Slf4j
@Component
public class RedisService {

    private final RedisTemplate redisTemplate;



    public RedisService(@Qualifier("redisTemplate") RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 存储数据至hash队列中
     * @param hKey hash队列 键
     * @param map  待存储的map数据
     * @param <T>
     * @throws Exception
     */
    public <T> void hashPutAll(String hKey,Map<String,T> map) throws Exception{
        redisTemplate.opsForHash().putAll(hKey,map);
    }

    /**
     * 获取hash队列中的value值
     * @param hKey hash队列 键
     * @param oKey hash队列中存储的hash的键
     * @return
     */
    public Object hashGet(String hKey,String oKey ) throws Exception{
        return redisTemplate.opsForHash().get(hKey, oKey);
    }

    /**
     * 有序集合添加.一次只增加一个K-V
     *
     * @param key   zset的Key名
     * @param value 值
     * @param score 权重，一般以权重(0-9)拼接时间戳组成
     * @return boolean  新增一个有序集合，存在的话为false，不存在的话为true
     */
    public <T> boolean zsetAdd(String key, T value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }
    /**
     * 有序集合添加.一次向set中增加多个值
     * @param key   zset的Key名
     * @param sets 值
     * @return boolean  新增一个有序集合，存在的话为false，不存在的话为true
     */
    public <T> void zsetAddBatch(String key, Set<T> sets) {
        if(sets==null||sets.size()==0){
            return ;
        }
        double score = Long.valueOf(5+""+ (System.currentTimeMillis())).doubleValue();
        for(T value : sets){
            redisTemplate.opsForZSet().add(key, value, score);
        }
    }
    /**
     * 获取超时时间
     */
    public Long getExpireTime(String key) {
        return redisTemplate.getExpire(key);
    }


    /**
     * 设置超时时间
     *
     * @param key
     * @param secExpireTime 单位为秒
     * @return
     */
    public boolean setExpireTime(String key, long secExpireTime) {
        return redisTemplate.expire(key, secExpireTime, TimeUnit.SECONDS);
    }

    /**
     *
     * @param key
     * @param secExpireTime
     * @param timeUnit 可自定义单位
     * @return
     */
    public boolean setExpireTime(String key, long secExpireTime,TimeUnit timeUnit ) {
        return redisTemplate.expire(key, secExpireTime, timeUnit);
    }


    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            log.error("set key value error", e);
        }
        return result;
    }


    /**
     * 如果键已存在则返回false,不更新,防止覆盖。使用pipeline批处理方式(不关注返回值)
     *
     * @param key
     * @param value
     * @param expireTime 单位为秒
     * @return
     */
    public boolean setNX(final String key, final Object value, final Long expireTime) {
        boolean result = (boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) {
                try {
                    JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
                    byte[] oValue = jdkSerializer.serialize(value);
                    return connection.setNX(key.getBytes(), oValue);
                } catch (Exception e) {
                    log.error("setNX RedisCallback error ;{}", e.getMessage());
                    return Boolean.FALSE;
                }
            }
        });
        if (result && expireTime != null) {
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); //设置超时时间，释放内存
        }
        return result;
    }

    public <T> void setObj(String key, T value,long expireTime) {
        redisTemplate.opsForValue().set(key, value);
        if (expireTime >0) {
            //设置超时时间，释放内存
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
        }
    }

    /**
     * 写入缓存设置时效时间
     *
     * @param key
     * @param value
     * @param expireTime 单位为秒
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            log.error("set key value expireTime error", e);
        }
        return result;
    }

    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public void remove(final List<String> keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量删除key
     *
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public <T> T get(final String key) {
        T result = null;
        ValueOperations<Serializable, T> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 哈希 添加
     *
     * @param key
     * @param hashKey
     * @param value
     */
    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }
    /**
     * 哈希 批量添加
     *
     * @param key
     * @param value
     */
    public void hmSet(String key, Map<String,String> value) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
       hash.putAll(key, value);
    }

    /**
     * 哈希 删除
     *
     * @param key
     * @param hashKey
     */
    public void hmRemove(String key, Object... hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.delete(key, hashKey);
    }

    /**
     * 哈希获取数据
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Object hmGet(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 列表添加
     *
     * @param k
     * @param v
     */
    public void lPush(String k, Object v) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }

    /**
     * 列表获取
     *
     * @param k
     * @param l
     * @param l1
     * @return
     */
    public List<Object> lRange(String k, long l, long l1) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }

    /**
     * 集合添加
     *
     * @param key
     * @param value
     */
    public void add(String key, Object value) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key, value);
    }


    /**
     * 集合(Set): 添加
     *
     * @param key
     * @param values
     * @return 成功添加数量
     */
    public long sAdd(String key, Object... values) {
        SetOperations<String, Object> setOperations = getSetOperations();
        return setOperations.add(key, values);
    }

    /**
     * 集合(Set): 判断值是否存在集合中
     *
     * @param key
     * @param value
     * @return
     */
    public boolean sExist(String key, Object value) {
        SetOperations<String, Object> opsForSet = getSetOperations();
        return opsForSet.isMember(key, value);
    }

    /**
     * 集合(Set): 获取集合中所有的元素
     *
     * @param key
     * @return
     */
    public Set<Object> sGetAllMembers(String key) {
        SetOperations<String, Object> opsForSet = getSetOperations();
        return opsForSet.members(key);
    }

    /**
     * 集合(Set): 删除集合中元素
     *
     * @param key
     * @param values
     * @return 成功删除的数量
     */
    public long sRemove(String key, Object... values) {
        SetOperations<String, Object> opsForSet = getSetOperations();
        return opsForSet.remove(key, values);
    }

    /**
     * 集合获取
     *
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     *
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key, value, scoure);
    }
    /**
     * 有序集合添加.一次只增加一个K-V
     *
     * @param key   zset的Key名
     * @param value 值
     * @param score 权重，一般以权重(0-9)拼接时间戳组成
     * @return boolean  新增一个有序集合，存在的话为false，不存在的话为true
     */
    public boolean zsetAdd2(String key, Object value, double score) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.add(key, value, score);
    }

    /**
     * 删除指定zset的某些元素，
     *
     * @param key    键
     * @param values 多个的值的数组。注意值的类型要与redis存储的相同
     * @return 返回成功删除的个数，当值不存在时删除的个数会少于指定个数
     */
    public Long zsetRemove(String key, Object... values) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.remove(key, values);
    }
    public <T> Long zSetRemove(String key, Collection<T> values) {
        long count = 0;
        Object[] valueArray =  new Object[values.size()];
        try {
            ZSetOperations<String, Object> zSet = redisTemplate.opsForZSet();
            valueArray = values.toArray(valueArray);
            count = zSet.remove(key, valueArray);
            //log.info("指定邮件渠道,批量删除redis->{}集合中的数据，删除条数为:{}",key,count);
            valueArray = null;
        } catch (Exception e) {
            log.error("短信，批量删除redis->{}集合中的数据出现异常", key, e);
        }
        return count;
    }


    /**
     * 有序集合获取
     * 根据分数
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeByScore(key, scoure, scoure1);
    }

    /**
     * 有序集合获取
     * 获取指定索引之间的set集合
     * @param key 键
     * @param start 最小索引
     * @param end 最大索引
     * @return 这个zset中指定的数据集合
     */
    public Set<ZSetOperations.TypedTuple<Object>> zsetRangeWithScore(String key, long start, long end) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeWithScores(key, start, end);
    }
    /**
     * 有序集合(ZSet):通过管道批量增加zSet数据并排序,如果key存在为追加，不存在为新建zset
     * <p>通过list传参，此list共用一个score</p>
     *
     * @param key     键
     * @param objects 传的数据列表
     * @param score   分数值
     */
    public <T> long zSetAddByTuple(String key, List<T> objects, double score) {
        return zSetAddByTupleColl(key, objects, score);
    }

    /**
     * 有序集合(ZSet):通过管道批量增加zSet数据并排序,如果key存在为追加，不存在为新建zset
     * <p>通过list传参，此list共用一个score</p>
     *
     * @param key     键
     * @param objects 传的数据列表
     * @param score   分数值
     */
    public <T> long zSetAddByTuple(String key, Set<T> objects, double score) {
        return zSetAddByTupleColl(key, objects, score);
    }
    /**
     * 有序集合(ZSet):通过管道批量增加zSet数据并排序,如果key存在为追加，不存在为新建zset
     * <p>通过list传参，此list共用一个score</p>
     *
     * @param key     键
     * @param objects 传的数据列表
     * @param score   分数值
     */
    public <T> long zSetAddByTupleColl(String key, Collection<T> objects, double score) {
        if (objects == null || objects.isEmpty()){
            return 0L;
        }
        long count = 0L;
        Set<ZSetOperations.TypedTuple<T>> tuples = new HashSet<ZSetOperations.TypedTuple<T>>();
        try{
            for (T object : objects) {
                ZSetOperations.TypedTuple<T> objectTypedTuple = new DefaultTypedTuple<T>(object, score);
                tuples.add(objectTypedTuple);
            }
            count = redisTemplate.opsForZSet().add(key, tuples);
        }catch (Exception e){
            log.error("微信发送:保存{}条消息至redis--{}集合发生异常",count,key,e);
        }
        return count;
    }

    /**
     * 通过管道批量增加zset数据并排序,如果key存在为追加，不存在为新建zset
     * <p>通过hashmap传参，以score为键，value为值，适用于score不重复的数据</p>
     *
     * @param key      键
     * @param valueMap 多个值的map，以score为键，以value为值
     */
    public void zsetAddByTuples(String key, HashMap<Double, Object> valueMap) {
        if (valueMap.isEmpty()){
            return;}
        Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<ZSetOperations.TypedTuple<Object>>();
        Iterator<Map.Entry<Double, Object>> iterator = valueMap.entrySet().iterator();
        double min = 0;
        double max = 0;
        while (iterator.hasNext()) {
            Map.Entry<Double, Object> entry = iterator.next();
            double score = entry.getKey();
            Object object = entry.getValue();
            ZSetOperations.TypedTuple<Object> objectTypedTuple = new DefaultTypedTuple<Object>(object, score);
            tuples.add(objectTypedTuple);
            min = score > min ? min : score;
            max = score > max ? score : max;
        }
        redisTemplate.opsForZSet().add(key, tuples);
    }

    /**
     * 有序集合获取
     * 根据index索引获取（分数越小越在前面）
     * @param key
     * @param start
     * @param end
     * @return
     */
    public  <T> Set<T> zRange (String key, long start, long end) {
        try {
            ZSetOperations<String, T> zset = redisTemplate.opsForZSet();
            return zset.range(key, start, end);
        } catch (Exception e) {
           log.error("获取redis有序集合异常！",e);
            return null;
        }
    }

    /**
     * 根据分数范围移除
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long removeRangeByScore(String key, double min, double max) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.removeRangeByScore(key,min,max);
    }

    /**
     * 移除有数据集合的值
     * @param key
     * @param values
     * @return
     */
    public Long  zRemove(String key,Object values){
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return  zset.remove(key,values);
    }







    /**
     * 获取自增
     */
    public Long increment() {
        return redisTemplate.opsForValue().increment("increment", 1);
    }

    /**
     * 获取自增
     *
     * @param key 自增KEY
     * @return
     */
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key, 1);
    }

    /**
     * 获取自增
     *
     * @param key        自增KEY
     * @param expireTime 失效时间，单位为秒
     * @return
     */
    public Long increment(String key, Integer expireTime) {
        if (expireTime == null || StringUtils.isEmpty(key)) {
            throw new NullPointerException("失效时间和KEY不能为空");
        }
        Long increment = this.increment(key);
        Boolean hasSuccess = redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
        if (!hasSuccess) {
            throw new IllegalArgumentException("设置失效时间失败: " + key);
        }
        return increment;
    }

    private SetOperations<String, Object> getSetOperations() {
        return redisTemplate.opsForSet();
    }



    /**
     * 删除一个锁
     *
     * @param lockKey
     * @return
     */
    public boolean unlock(String lockKey) {
        return redisTemplate.delete(lockKey);
    }


    /**
     *
     * @description 从redis查询并删除所查询的数据
     * @param key
     * @param start 数据开始下标 从0开始
     * @param end
     *        数据截止下标
     * @return 查询结果
     * @author wangp
     * @datetime 2019年6月21日 上午11:09:39
     */
    public <T> Set<T> zRangeAndZRem(String key, int start, int end)
    {
        Set<T> sets = null;
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);
            //设置参数
            String [] params = new String[2];
            params[0] = String.valueOf(start);
            params[1] = String.valueOf(end);
            List<String> resList;
            Set<byte[]> bytes=new HashSet<>();
            //RedisScript的resultType的选择（result值类型）此处为List
            RedisScript redisScript= RedisScript.of("local element=redis.call('ZRANGE', KEYS[1], ARGV[1], ARGV[2]) if #element ~= 0 then redis.call('ZREMRANGEBYRANK', KEYS[1], 0, #element - 1) end return element",List.class);
            //RedisSerializer()的选择（keys,args,result值的序列化规则）
            //定义 keys,argv和result序列化方式
            resList = (List<String>) redisTemplate.execute(redisScript,new StringRedisSerializer(),new StringRedisSerializer(),keys,params);
            assert resList != null;
            for(String msg : resList){
                bytes.add(msg.getBytes());
            }
            if(bytes.size() > 0){
                sets = SerializationUtils.deserialize(bytes,redisTemplate.getValueSerializer());
            }
        } catch (Exception e) {
            //异常处理机制
            log.error("获取数据异常:", e);
            throw e;
        }
        return sets;
    }

    /**
     *
     * @description 从redis查询并删除所查询的数据
     * @param key
     * @param end
     *        数据截止下标
     * @return 查询结果
     * @author wangp
     * @datetime 2019年6月21日 上午11:09:39
     */
    public <T> Set<T> zRangeAndZRem(String key, int end)
    {
        return zRangeAndZRem(key, 0, end);

    }

}
